
import { proxyRefs } from '../reactivity'
import { shallowReadonly } from '../reactivity/reactive'
import { emit } from './componentEmits'
import { initProps } from './componentProps'
import { PublicInstanceProxyHandlers } from './componentPublicInstance'
import { initSlots } from './componentSlots'
import type { componentInstance, rawSlots, render, slots, vnode, vnodeProps, vnodeType, vnodeType_component } from './index.d'

/**当前组件实例。因为 getCurrentInstance 函数拿不到，所以需要借助全局变量（在执行setup的时候赋值，这样才能保证setup的时候拿到正确的数据） */
let currentInstance: componentInstance | null = null

/**创建组件实例并返回
 * @param vnode 该组件的虚拟节点
 * @param parent 父组件实例
 * @returns 
 */
export function createComponentInstance(vnode: vnode, parent: componentInstance): componentInstance {
    // console.log("createComponentInstance时，父组件是", parent);
    const component: componentInstance = {
        isMounted: false,
        vnode,
        parent, //parent的来源是在 patch 函数递归调用的时候赋值的，根组件是undefined，其它是一层层赋值的 
        provides: parent ? parent.provides : {}, //如果parent存在就用parent的，避免parent是undefined  （在这里只是初始化，真正的赋值在 src\runtime-core\apiInject.ts 的 provide 函数）
        slots: {},
        emit: () => { },
        setupState: {},
        props: {},
        next: null,
        type: vnode.type, //把 vnode 的type挂载在组件实例上 上
        subTree: { //初始化的subtree，下面这些初始化的值不会影响代码运行
            el: null,
            type: '',
            shapeFlag: 1,
            key: 0,
            component: null
        }
    }
    component.emit = emit.bind(null, component) as any //在这里使用bind手动传入第一个参数，用户在使用的时候就不用传递第一个参数了
    return component
}
/**给实例加工 ，处理props和slots，执行setup并处理返回值、挂载proxy、挂载render等
 * @param instance 组件实例
 */
export function setupComponent(instance: componentInstance) {
    initProps(instance, instance.vnode.props)
    initSlots(instance, instance.vnode.children)
    setupStatefulComponent(instance)
}
/**初始化一个有状态的component。
 * - 在里面创建proxy(实现在render中this.用setup的东西、能使用$el获取组件根容器DOM) 和ctx
 * - 调用setup（没有setup的就调用vue2那部分的） 
 * @param instance 组件实例
 */
export function setupStatefulComponent(instance: componentInstance) {
    //拿到用户写的setup，执行
    /**组件的类型 */
    const component = instance.type
    //在这里挂载proxy，让 src\runtime-core\renderer.ts 中的 setupRenderEffect函数 能在调用render的时候call绑定this。
    instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers) //在这里使用 _ 传入instance，这样在get操作的时候就可以解构出来
    /**拿到组件的setup */
    const { setup } = component as vnodeType_component //因为这里是处理component的函数，所以一定是这个类型
    if (setup) {//用户有可能没写setup。写了的就调用setup
        setCurrentInstance(instance) //赋值当前组件实例
        /**setup返回值，可能返回function 或 Object */
        const setupResult = setup(shallowReadonly(instance.props), { emit: instance.emit }) //在这里进行setup的传参。props需要用shallowReadonly包裹，因为是只读属性
        handleSetupResult(instance, setupResult);
        setCurrentInstance(null) //执行完setup后就清空
    }
}
/**处理 setupResult，setup返回值不同，得到的东西也不一样
 * - 如果是函数的话，就是渲染函数，如果是对象的话，里面的东西就拿出来
 * - 在这里进行 proxyRefs ，让render中可以直接使用ref
 * @param instance 组件实例
 * @param setupResult setup的返回值
 */
function handleSetupResult(instance: componentInstance, setupResult: any) {
    //TODO function
    if (typeof setupResult === 'object') {
        instance.setupState = proxyRefs(setupResult) //挂载结果，并使用 proxyRefs 进行代理 ，让render中可以直接使用ref
    }
    finishComponentSetup(instance)
}
/**完成组件setup，挂载render
 * @param instance 组件实例
 */
function finishComponentSetup(instance: componentInstance) {
    const component = instance.type as vnodeType_component
    if (component.render) {//如果传递了render函数，那么就挂载到组件实例身上  
        instance.render = component.render
    }
}




/**获得当前组件实例  **必须在setup中才能使用** */
export function getCurrentInstance() {
    return currentInstance
}
/**设置当前组件实例，封装成函数，可以方便的知道这个全局变量是什么时候被改变的 */
function setCurrentInstance(instance: componentInstance | null) {
    currentInstance = instance
}

